Pengembalian barang merupakan faktor biaya yang sangat signifikan bagi retail online. Rata-rata setengah dari semua pesanan pelanggan adalah pengembalian barang. Topik ini akan menjadi semakin penting dengan diperkenalkannya petunjuk perlindungan konsumen. Oleh karena itu, tingkat pengembalian yang lebih rendah akan menjadi faktor utama dalam keunggulan kompetitif dalam ritel online.
Berdasarkan data pembelian historis dari sebuah toko online, sebuah model harus dipelajari untuk menghasilkan prediksi probabilitas bahwa pembelian tertentu dikonversi menjadi pengembalian berdasarkan data pembelian baru dari toko tersebut. Untuk tujuan ini, data historis juga berisi data pembelian dan pengiriman sebagai atribut produk dan pelanggan yang berbeda. Informasi "return yes/no" juga diketahui dari data historis. Data historis berisi data pembelian dan pengiriman toko online 2014.
Target: returnShipment | returnShipment: Pengembalian (1=ya/dikembalikan, 0=tidak/disimpan). Memprediksi apakah ada pengembalian barang pada pembelian berdasarkan data pembelian baru toko tersebut. Atribut target "returnShipment" dari item pesanan. Nilai "0" berarti "barang disimpan" dan nilai "1" berarti "barang dikembalikan".
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import os, glob
#load dataset
df = pd.read_csv("E:\\data\\orders_train.txt", sep=';', na_values=["?"])
# Menampilkan dataframe
df.head()
| orderItemID | orderDate | deliveryDate | itemID | size | color | manufacturerID | price | customerID | salutation | dateOfBirth | state | creationDate | returnShipment | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 1 | 2012-04-01 | 2012-04-03 | 186 | m | denim | 25 | 69.90 | 794 | Mrs | 1965-01-06 | Baden-Wuerttemberg | 2011-04-25 | 0 |
| 1 | 2 | 2012-04-01 | 2012-04-03 | 71 | 9+ | ocher | 21 | 69.95 | 794 | Mrs | 1965-01-06 | Baden-Wuerttemberg | 2011-04-25 | 1 |
| 2 | 3 | 2012-04-01 | 2012-04-03 | 71 | 9+ | curry | 21 | 69.95 | 794 | Mrs | 1965-01-06 | Baden-Wuerttemberg | 2011-04-25 | 1 |
| 3 | 4 | 2012-04-02 | NaN | 22 | m | green | 14 | 39.90 | 808 | Mrs | 1959-11-09 | Saxony | 2012-01-04 | 0 |
| 4 | 5 | 2012-04-02 | 1990-12-31 | 151 | 39 | black | 53 | 29.90 | 825 | Mrs | 1964-07-11 | Rhineland-Palatinate | 2011-02-16 | 0 |
# Melihat dimensi dataset (jumlah baris dan kolom)
df.shape
(481092, 14)
# Informasi detail dataset
df.info()
<class 'pandas.core.frame.DataFrame'> RangeIndex: 481092 entries, 0 to 481091 Data columns (total 14 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 orderItemID 481092 non-null int64 1 orderDate 481092 non-null object 2 deliveryDate 441673 non-null object 3 itemID 481092 non-null int64 4 size 481092 non-null object 5 color 480949 non-null object 6 manufacturerID 481092 non-null int64 7 price 481092 non-null float64 8 customerID 481092 non-null int64 9 salutation 481092 non-null object 10 dateOfBirth 432203 non-null object 11 state 481092 non-null object 12 creationDate 481092 non-null object 13 returnShipment 481092 non-null int64 dtypes: float64(1), int64(5), object(8) memory usage: 51.4+ MB
# Menampilkan tipe data dari setiap kolom
df.dtypes
orderItemID int64 orderDate object deliveryDate object itemID int64 size object color object manufacturerID int64 price float64 customerID int64 salutation object dateOfBirth object state object creationDate object returnShipment int64 dtype: object
# Mengecek jumlah data kosong
df.isnull().sum()
orderItemID 0 orderDate 0 deliveryDate 39419 itemID 0 size 0 color 143 manufacturerID 0 price 0 customerID 0 salutation 0 dateOfBirth 48889 state 0 creationDate 0 returnShipment 0 dtype: int64
# Visualisasi persentase data kosong
import plotly.graph_objects as go
# Hitung persentase data kosong per kolom
missing_percentage = df.isnull().mean() * 100
# Membuat bar chart menggunakan plotly
fig = go.Figure()
# Menambahkan data untuk bar chart
fig.add_trace(go.Bar(
x=missing_percentage.index,
y=missing_percentage,
text=[f'{v:.2f}%' for v in missing_percentage],
textposition='outside',
marker=dict(color='skyblue'),
))
# Menambahkan judul dan label sumbu
fig.update_layout(
title=dict(
text="Persentase Data Kosong",
x=0.5,
y=0.9,
font=dict(size=16)
),
yaxis_title="Persentase Data Kosong (%)",
template="plotly_white",
)
# Menampilkan plot
fig.show()
Dari hasil pengecekan data kosong, kolom salutation memiliki persentase data kosong tertinggi, yaitu 10.16%. Kolom deliveryDate memiliki persentase data kosong 8.19% dan kolom color 0.03% data kosong. Sebagian kolom lainnya memiliki persentase data kosong 0% yang menunjukkan bahwa data pada kolom-kolom ini relatif lengkap.
import plotly.express as px
# Melihat perngaruh data kosong terhadap data returnShipemnt
vis = df.copy()
df_na = [a for a in vis.columns if vis[a].isna().sum() > 0] # Menentukan kolom dengan data kosong
# Perulangan untuk setiap fitur yang memiliki nilai kosong
for a in df_na:
# Membuat salinan data dan mengganti NaN dengan no dan yang lainnya menjadi yes
data_na = vis.copy()
data_na[a] = np.where(data_na[a].isna(), 'no', 'yes')
# Membuat plot menggunakan Plotly Express
fig = px.histogram(
data_na,
x='returnShipment',
color=a,
barmode='group',
title=f'Distribusi Data Kosong pada Fitur {a}',
labels={'returnShipment': 'Return Shipment'},
category_orders={'returnShipment': ['yes', 'no']},
color_discrete_map={'no': 'indianred', 'yes': 'cornflowerblue'}
)
fig.update_layout(
title_x=0.5,
legend_title=a
)
# Menampilkan grafik
fig.show()
# Melakukan Uji Chi-Square untuk melihat apakah ada hubungan antara keberadaan data kosong
# pada fitur-fitur yang memiliki data kosong dengan kemungkinan pengembalian barang (returnShipment)
from scipy.stats import chi2_contingency
# Menyimpan hasil uji Chi-Square
chi_square_results = {}
# Loop untuk setiap kolom dengan data kosong
for a in df_na:
# Mengganti nilai kosong (NaN) menjadi 1 (kosong), dan nilai lainnya menjadi 0 (tidak kosong)
data_na = vis.copy()
data_na[a] = data_na[a].isna().astype(int)
# Membuat tabel kontingensi
contingency_table = pd.crosstab(data_na['returnShipment'], data_na[a])
# Melakukan uji Chi-Square
chi2, p, dof, expected = chi2_contingency(contingency_table)
# Menyimpan hasil uji Chi-Square
chi_square_results[a] = {
'chi2_statistic': chi2,
'p_value': p,
'degrees_of_freedom': dof
}
# Menampilkan hasil
for feature, result in chi_square_results.items():
print(f"Uji Chi-Square untuk {feature}:")
print(f"Chi-Square Statistic: {result['chi2_statistic']}")
print(f"P-Value: {result['p_value']}")
print(f"Degree of Freedom: {result['degrees_of_freedom']}")
if p < 0.05:
print("Terdapat hubungan signifikan antara nilai kosong dan kategori returnShipment.\n")
else:
print("Tidak terdapat hubungan signifikan antara nilai kosong dan kategori returnShipment.\n")
Uji Chi-Square untuk deliveryDate: Chi-Square Statistic: 40019.094469339616 P-Value: 0.0 Degree of Freedom: 1 Terdapat hubungan signifikan antara nilai kosong dan kategori returnShipment. Uji Chi-Square untuk color: Chi-Square Statistic: 105.91419902455746 P-Value: 7.699792024944289e-25 Degree of Freedom: 1 Terdapat hubungan signifikan antara nilai kosong dan kategori returnShipment. Uji Chi-Square untuk dateOfBirth: Chi-Square Statistic: 7.924416076770826 P-Value: 0.0048772082880166965 Degree of Freedom: 1 Terdapat hubungan signifikan antara nilai kosong dan kategori returnShipment.
Tingkat signifikansi (P-Value) yang digunakan adalah 0.05.
# Salin data untuk proses preproses
df2 = df.copy()
# Mengisinya dengan color yang paling sering muncul (mode)
df2.color.fillna(df2.color.mode()[0], inplace=True)
df2.isnull().sum()
orderItemID 0 orderDate 0 deliveryDate 39419 itemID 0 size 0 color 0 manufacturerID 0 price 0 customerID 0 salutation 0 dateOfBirth 48889 state 0 creationDate 0 returnShipment 0 dtype: int64
# Cek nilai unik color
df2.color.unique()
array(['denim', 'ocher', 'curry', 'green', 'black', 'brown', 'red',
'mocca', 'anthracite', 'olive', 'petrol', 'blue', 'grey', 'beige',
'ecru', 'turquoise', 'magenta', 'purple', 'pink', 'khaki', 'navy',
'habana', 'silver', 'white', 'nature', 'stained', 'orange',
'azure', 'apricot', 'mango', 'berry', 'ash', 'hibiscus', 'fuchsia',
'blau', 'dark denim', 'mint', 'ivory', 'yellow', 'bordeaux',
'pallid', 'ancient', 'baltic blue', 'almond', 'aquamarine',
'brwon', 'aubergine', 'aqua', 'dark garnet', 'dark grey',
'avocado', 'creme', 'champagner', 'cortina mocca',
'currant purple', 'cognac', 'aviator', 'gold', 'ebony',
'cobalt blue', 'kanel', 'curled', 'caramel', 'antique pink',
'darkblue', 'copper coin', 'terracotta', 'basalt', 'amethyst',
'coral', 'jade', 'opal', 'striped', 'mahagoni', 'floral',
'dark navy', 'dark oliv', 'vanille', 'ingwer', 'iron', 'graphite',
'leopard', 'oliv', 'bronze', 'crimson', 'lemon', 'perlmutt'],
dtype=object)
Nilai unik color terlalu banyak, karena itu perlu dilakukan grouping kategori yang jarang muncul. Untuk color yang kemunculannya kurang dari 5000 kali akan dikategorikan ke dalam 'other'
# Hitung frekuensi masing-masing color
color_counts = df2['color'].value_counts()
# Tentukan threshold untuk kategori yang jarang muncul
threshold = 3500 # Kemunculan kurang dari 3500 kali
# Ganti color yang jarang muncul dengan 'other'
df2['color'] = df2['color'].apply(lambda x: x if color_counts[x] >= threshold else 'other')
# Cek nilai unik color
df2.color.unique()
array(['denim', 'ocher', 'other', 'green', 'black', 'brown', 'red',
'mocca', 'anthracite', 'olive', 'petrol', 'blue', 'grey', 'ecru',
'turquoise', 'purple', 'pink', 'white', 'stained', 'orange',
'berry', 'ash', 'aquamarine', 'aubergine'], dtype=object)
# Jumlah masing-masing warna
df2['color'].value_counts().head()
black 86395 blue 48180 grey 42273 other 41916 red 39074 Name: color, dtype: int64
Setelah dikategorikan dengan menentukan threshold, untuk kategori color yang banyak muncul yaitu black.
Pada atribut deliveryDate dan dateOfBirth karena tipe data 2 atribut tersebut object, maka sebelum penanganan data kosong melakukan konversi tipe data terlebih dahulu dari tipe object ke tipe datetime.
# Mengubah tipe data deliveryDate dan dateOfBirth dari tipe data objek ke tipe datatime
# Mambahkan parameter errors untuk mengabaikan out-of-bound
# Konversi data deliveryDate dari objek menjadi datetime
df2["deliveryDate"] = pd.to_datetime(df2["deliveryDate"], format='%Y-%m-%d', errors = 'coerce')
# Konversi data dateOfBirth dari objek menjadi datetime
df2["dateOfBirth"] = pd.to_datetime(df2["dateOfBirth"], format='%Y-%m-%d', errors = 'coerce')
# Cek tipe data
df2.dtypes
orderItemID int64 orderDate object deliveryDate datetime64[ns] itemID int64 size object color object manufacturerID int64 price float64 customerID int64 salutation object dateOfBirth datetime64[ns] state object creationDate object returnShipment int64 dtype: object
# Melihat nilai unik dari DeliveryDate
unique_years = df2['deliveryDate'].dt.year.unique()
print(unique_years)
[2012. nan 1990. 2013.]
# Melihat data orderDate dan deliveryDate
df2[['orderDate', 'deliveryDate']].head(10)
| orderDate | deliveryDate | |
|---|---|---|
| 0 | 2012-04-01 | 2012-04-03 |
| 1 | 2012-04-01 | 2012-04-03 |
| 2 | 2012-04-01 | 2012-04-03 |
| 3 | 2012-04-02 | NaT |
| 4 | 2012-04-02 | 1990-12-31 |
| 5 | 2012-04-02 | 1990-12-31 |
| 6 | 2012-04-02 | 1990-12-31 |
| 7 | 2012-04-02 | 2012-04-03 |
| 8 | 2012-04-02 | 2012-04-03 |
| 9 | 2012-04-02 | 2012-04-03 |
Untuk penanganan data kosong pada fitur deliveryDate terdapat beberapa pengecekan dan proses lainnya.
Dari penemuan tersebut, sebelum menangani data kosong fitur deliveryDate, dilakukan penyesuaian terlebih dahulu dengan mengganti tahun dan bulan pada deliveryDate yang memiliki tahun 1990 disesuaikan dengan tahun dan bulan pada orderDate. Sedangkan untuk tanggalnya diisi random 3-5 hari dari tanggal pemesanan (orderDate).
import random
# Mengubah kolom 'orderDate' menjadi tipe datetime
df2['orderDate'] = pd.to_datetime(df2['orderDate'], errors='coerce')
# Fungsi untuk mengganti tahun, bulan, dan hari deliveryDate mengacu pada orderDate
def adjust_delivery_date(row):
# Memeriksa apakah tahun deliveryDate adalah 1990
if row['deliveryDate'].year == 1990:
# Ambil bulan dan tahun dari orderDate
year = row['orderDate'].year
month = row['orderDate'].month
# Ambil + 3-5 hari dari orderDate
day = row['orderDate'].day + random.choice([3, 5])
# Menghindari kesalahan jika hari yang ditambahkan melebihi batas bulan
try:
adjusted_date = row['deliveryDate'].replace(year=year, month=month, day=day)
except ValueError:
# Jika hari yang ditambahkan melebihi batas, set ke hari terakhir bulan tersebut
adjusted_date = row['deliveryDate'].replace(year=year, month=month, day=1) + pd.Timedelta(days=-1)
return adjusted_date
return row['deliveryDate']
# Terapkan fungsi mengganti tahun, bulan, dan hari
df2['deliveryDate'] = df2.apply(adjust_delivery_date, axis=1)
# Melihat data orderDate dan deliveryDate setelah penyesuaian tahun deliveryDate
df2[['orderDate', 'deliveryDate']].head(10)
| orderDate | deliveryDate | |
|---|---|---|
| 0 | 2012-04-01 | 2012-04-03 |
| 1 | 2012-04-01 | 2012-04-03 |
| 2 | 2012-04-01 | 2012-04-03 |
| 3 | 2012-04-02 | NaT |
| 4 | 2012-04-02 | 2012-04-05 |
| 5 | 2012-04-02 | 2012-04-07 |
| 6 | 2012-04-02 | 2012-04-07 |
| 7 | 2012-04-02 | 2012-04-03 |
| 8 | 2012-04-02 | 2012-04-03 |
| 9 | 2012-04-02 | 2012-04-03 |
# Melihat nilai unik dari DeliveryDate setelah penyesuaian tahun
unique_years = df2['deliveryDate'].dt.year.unique()
print(unique_years)
[2012. nan 2013.]
Untuk mengisi nilai kosong pada deliveryDate menggunakan jarak waktu relatif dari orderDate, dengan menambahkan 2-5 hari setelah orderDate.
# Cek data kosong
df2.isnull().sum()
orderItemID 0 orderDate 0 deliveryDate 39419 itemID 0 size 0 color 0 manufacturerID 0 price 0 customerID 0 salutation 0 dateOfBirth 48892 state 0 creationDate 0 returnShipment 0 dtype: int64
# Fungsi untuk mengisi deliveryDate berdasarkan orderDate
def fill_delivery_date(row):
if pd.isna(row['deliveryDate']): # Jika deliveryDate kosong
# Tambahkan antara 2 hingga 5 hari ke orderDate
penambahan_hari = random.randint(2, 5)
return row['orderDate'] + pd.Timedelta(days=penambahan_hari)
return row['deliveryDate'] # Jika tidak kosong, biarkan deliveryDate tetap
# Terapkan fungsi ke dataframe
df2['deliveryDate'] = df2.apply(fill_delivery_date, axis=1)
# Cek data kosong
df2.isnull().sum()
orderItemID 0 orderDate 0 deliveryDate 0 itemID 0 size 0 color 0 manufacturerID 0 price 0 customerID 0 salutation 0 dateOfBirth 48892 state 0 creationDate 0 returnShipment 0 dtype: int64
Untuk menangani data kosong fitur dateOfBirth dilakukan beberapa proses lain seperti cek tahun maksimum dan minimum dan cek nilai unik fitur dateOfBirth untuk melihat rentang tahun. Kemudian lihat usia per tahun 2014, untuk memfilter usia yang tidak relevan seperti terlalu muda (15 tahun ke bawah) atau terlalu tua (80 tahun ke atas).
# Melihat nilai maksimal dan minimal dateOfBirth
print("Tanggal Maks dateOfBirth :", df2.dateOfBirth.max())
print("Tanggal Min dateOfBirth :", df2.dateOfBirth.min())
Tanggal Maks dateOfBirth : 2013-06-27 00:00:00 Tanggal Min dateOfBirth : 1900-11-19 00:00:00
# Melihat nilai unik dari dateOfBirth
df2['dateOfBirth'].dt.year.unique()
array([1965., 1959., 1964., 1948., nan, 1963., 1953., 1961., 1978.,
1968., 1940., 1966., 1945., 1943., 1958., 1955., 1960., 1900.,
1956., 1977., 1954., 1969., 1971., 1973., 1962., 1957., 1970.,
1950., 1967., 1951., 1974., 1972., 1949., 1952., 1980., 1976.,
1927., 1938., 1982., 1990., 1946., 1901., 1981., 1944., 1985.,
1986., 1983., 1979., 1975., 1947., 1988., 1984., 1942., 1987.,
1991., 1929., 1992., 1925., 1993., 1937., 1941., 1939., 2010.,
1931., 1908., 1936., 1934., 1920., 1933., 1989., 2011., 1999.,
1932., 1935., 1921., 1928., 1926., 2009., 1907., 1998., 2000.,
1996., 1911., 2005., 1906., 1995., 1903., 1994., 1930., 1912.,
2012., 1918., 1997., 1924., 1917., 2013.])
# Cek usia
cek_usia = df2.copy()
cek_usia['age'] = 2014 - cek_usia['dateOfBirth'].dt.year
# Menampilkan usia di atas 80 tahun atau di bawah 17 tahun
cek_usia[(cek_usia['age'] > 80) | (cek_usia['age'] < 17)][['dateOfBirth', 'age']].head(10)
| dateOfBirth | age | |
|---|---|---|
| 79 | 1900-11-19 | 114.0 |
| 374 | 1927-04-10 | 87.0 |
| 570 | 1900-11-19 | 114.0 |
| 571 | 1900-11-19 | 114.0 |
| 572 | 1900-11-19 | 114.0 |
| 573 | 1900-11-19 | 114.0 |
| 574 | 1900-11-19 | 114.0 |
| 575 | 1900-11-19 | 114.0 |
| 653 | 1901-09-25 | 113.0 |
| 654 | 1901-09-25 | 113.0 |
Di sini akan mengambil data tahun lahir valid di antara tahun 1935 sampai 1997 atau di kisaran usia 17-80 tahun. Untuk tahun lahir di luar ketentuan tersebut akan diganti, dan karena tahunnya bervariasi untuk mengganti tahunnya menggunakan median.
# Hitung median tahun yang valid (1935-1997)
tahun_valid = df2[df2['dateOfBirth'].dt.year.between(1935, 1997)]['dateOfBirth'].dt.year
median_tahun = tahun_valid.median()
# Ganti tahun yang tidak valid (di bawah 1935 atau di atas 1997) dengan median
df2['dateOfBirth'] = df2['dateOfBirth'].apply(
lambda x: pd.Timestamp(f"{int(median_tahun)}-{x.month:02d}-{x.day:02d}")
if x.year < 1935 or x.year > 1997 else x
)
# Melihat nilai maksimal dan minimal dateOfBirth setelah mengubah dengan median
print("Tanggal Maks dateOfBirth :", df2.dateOfBirth.max())
print("Tanggal Min dateOfBirth :", df2.dateOfBirth.min())
Tanggal Maks dateOfBirth : 1997-09-06 00:00:00 Tanggal Min dateOfBirth : 1935-01-04 00:00:00
Sama seperti halnya mengganti tahun yang tidak valid, untuk menangani data kosong karena tahunnya bervariasi untuk mengganti tahunnya menggunakan median.
# Fungsi untuk mengganti tahun yang hilang dengan median
def replace_with_median(row):
if pd.isna(row): # Jika data kosong (NaN) atau NaT
# Jika data kosong, ganti tahun dengan median
return pd.Timestamp(f"{int(median_tahun)}-01-01")
# Jika data tidak kosong, kembalikan baris tanpa perubahan
return row
# Terapkan fungsi ke dataframe
df2['dateOfBirth'] = df2['dateOfBirth'].apply(replace_with_median)
# Cek data kosong
df2.isnull().sum()
orderItemID 0 orderDate 0 deliveryDate 0 itemID 0 size 0 color 0 manufacturerID 0 price 0 customerID 0 salutation 0 dateOfBirth 0 state 0 creationDate 0 returnShipment 0 dtype: int64
# Melihat nilai unik dari size
df2['size'].unique()
array(['m', '9+', '39', 'xxl', '37', '43', '38', 'l', 'xl', '42', '41',
'unsized', 's', '10+', '40', '36', '152', '35', '34', '8+', '9',
'46', '6', '10', '25', '20', '5', '42+', '44', '4+', '8', '3',
'6+', '48', '7+', '50', '22', '12', '45', '7', '24', '36+', '39+',
'27', '32', '11', '26', '40+', '19', '21', '5+', '116', '2', '28',
'38+', '11+', '37+', '164', '4', '33', '29', '30', '18', '41+',
'1', '47', '31', '104', '128', '95', '3+', '140', '23', '13',
'3332', 'S', '44+', 'xxxl', '54', '52', '3432', '43+', '3434',
'49', '84', '56', '14', '13+', '76', '90', '85', '176', '88',
'45+', 'L', '46+', '80', '3632', '3832', '3634', '4032', 'xs',
'2+', '100', '3132', '58', '4034', '105', '3834', '12+', '2932',
'M', '110', '122', 'XXL', 'XL', 'XXXL', '4232', 'XS', '92', '96',
'3334'], dtype=object)
Dari pengecekan nilai unik dari fitur size, terdapat nilai dengan ukuran yang memiliki huruf kapital menjadi huruf kecil, seperti ukuran 'm' dan 'M'. Selain itu, terdapat tanda '+' yang mengarah pada perbedaan yang tidak perlu. Untuk itu, ukuran akan diubah menjadi kapital semua dan menghapus tanda '+'. Kemudian mengategorikan ukuran numerik menjadi ukuran dalam bentuk huruf.
# Normalisasi dan buang tanda '+'
df2['size'] = df2['size'].str.upper() # Ubah ke kapital semua
df2['size'] = df2['size'].str.replace(r'\+', '', regex=True) # Hapus tanda '+'
# Mengelompokkan ukuran berdasarkan angka
def categorize_size(size):
# Kategori ukuran standar
size_categories = ['XS', 'S', 'M', 'L', 'XL', 'XXL', 'XXXL']
if size in size_categories:
return size
try:
size_num = int(size)
# Kategorisasi berdasarkan ukuran angka
if size_num <= 10:
return 'XS'
elif 11 <= size_num <= 35:
return 'S'
elif 36 <= size_num <= 42:
return 'M'
elif 43 <= size_num <= 50:
return 'L'
elif 51 <= size_num <= 200:
return 'XL'
elif 201 <= size_num <= 1000:
return 'XXL'
elif size_num > 1000:
return 'XXXL'
except ValueError:
return 'UNSIZED'
# Terapkan fungsi ke dataframe untuk mengkategorikan ukuran
df2['size'] = df2['size'].apply(categorize_size)
# Cek hasil fitur size
df2['size'].unique()
array(['M', 'XS', 'XXL', 'L', 'XL', 'UNSIZED', 'S', 'XXXL'], dtype=object)
# Distribusi data kategorikal Size
# Menghitung frekuensi kategori
size_counts = df2['size'].value_counts().reset_index()
size_counts.columns = ['Kategori', 'Jumlah']
# Membuat bar chart
fig = px.bar(size_counts, x='Jumlah', y='Kategori', orientation='h',
title='Frekuensi Fitur Size', labels={'Jumlah': 'Jumlah', 'Kategori': 'Size'},
color='Jumlah', color_continuous_scale='Earth')
fig.update_layout(
title_x=0.5,
)
# Menampilkan plot
fig.show()
# Cek fitur dengan tipe data objek
df2.select_dtypes(include = ['object']).head()
| size | color | salutation | state | creationDate | |
|---|---|---|---|---|---|
| 0 | M | denim | Mrs | Baden-Wuerttemberg | 2011-04-25 |
| 1 | XS | ocher | Mrs | Baden-Wuerttemberg | 2011-04-25 |
| 2 | XS | other | Mrs | Baden-Wuerttemberg | 2011-04-25 |
| 3 | M | green | Mrs | Saxony | 2012-01-04 |
| 4 | M | black | Mrs | Rhineland-Palatinate | 2011-02-16 |
# Mengubah data tipe objek menjadi kategori
kategori = ['size', 'color', 'salutation', 'state']
# Looping untuk merubah tipe data'
for column in kategori:
df2[column] = df2[column].astype('category')
# Mengubah type data objek dengan format tanggal menjadi datetime
# Konversi data atribut creationDate menjadi datetime
df2["creationDate"] = df2["creationDate"].astype("datetime64[ns]")
# Cek tipe data
df2.dtypes
orderItemID int64 orderDate datetime64[ns] deliveryDate datetime64[ns] itemID int64 size category color category manufacturerID int64 price float64 customerID int64 salutation category dateOfBirth datetime64[ns] state category creationDate datetime64[ns] returnShipment int64 dtype: object
# Distribusi nilai untuk kolom dengan fitur numerik
df2[['price']].describe()
| price | |
|---|---|
| count | 481092.000000 |
| mean | 70.440229 |
| std | 45.502854 |
| min | 0.000000 |
| 25% | 34.900000 |
| 50% | 59.900000 |
| 75% | 89.900000 |
| max | 999.000000 |
# Visualisasi distribusi fitur price
import plotly.express as px
# Membuat histogram
fig = px.histogram(df2, x='price', nbins=30, title='Distribusi Fitur Price')
# Menambahkan label untuk sumbu x dan y
fig.update_layout(
xaxis_title="Price",
yaxis_title="Frekuensi",
title_x=0.5
)
# Menampilkan plot
fig.show()
Karena data numerik selain price merupakan data ID, maka untuk melihat distribusi fitur numerik hanya data price.
Dari visualisasi histogram distribusi harga, sebagian besar data terpusat di sisi kiri histogram, yaitu pada rentang harga yang lebih rendah. Ekor histogram memanjang ke kanan, menunjukkan bahwa ada beberapa data dengan harga yang jauh lebih tinggi. Batang tertinggi menunjukkan rentang harga yang paling sering muncul. Dalam gambar, rentang harga antara 0 dan sekitar 25-75 adalah yang paling umum. Ada kemungkinan adanya outlier pada rentang harga yang lebih tinggi mendekati 1000.
# Visualisasi outlier menggunakan Box Plot
fig = px.box(df2, y="price", title="Deteksi Outlier Fitur Price")
fig.update_layout(
title_x=0.5
)
fig.show()
# Visualisasi outlier fitur price dengan returnShipment
fig = px.box(df2, x="returnShipment", y="price")
fig.show()
# Menghitung IQR
Q1 = df2['price'].quantile(0.25)
Q3 = df2['price'].quantile(0.75)
IQR = Q3 - Q1
# Batas untuk mendeteksi outlier
lower_bound = Q1 - 1.5 * IQR
upper_bound = Q3 + 1.5 * IQR
# Deteksi outlier
outliers = df2[(df2['price'] < lower_bound) | (df2['price'] > upper_bound)]
print(f"Jumlah outlier: {len(outliers)}")
Jumlah outlier: 14017
# Mengatasi outlier, mengganti dengan batas maksimum
df2['price'] = np.where(df2['price'] > upper_bound, upper_bound, df2['price'])
# Distribusi fitur price
df2[['price']].describe()
| price | |
|---|---|
| count | 481092.000000 |
| mean | 69.267591 |
| std | 41.627030 |
| min | 0.000000 |
| 25% | 34.900000 |
| 50% | 59.900000 |
| 75% | 89.900000 |
| max | 172.400000 |
# Visualisasi outlier setelah menghapus outlier
fig = px.box(df2, y="price")
fig.show()
# Visualisasi distribusi fitur price
import plotly.express as px
# Membuat histogram
fig = px.histogram(df2, x='price', nbins=30, title='Distribusi Price')
# Menambahkan label untuk sumbu x dan y
fig.update_layout(
xaxis_title="Price",
yaxis_title="Frekuensi",
title_x=0.5
)
# Menampilkan plot
fig.show()
Dari visualisasi box plot dan histogarm setalah penghapusan outlier sudah tidak ada lagi outlier. Distribusi data dilihat pada kotak boxplot dan histogram menunjukkan bahwa sebagian besar data harga terkonsentrasi di antara nilai sekitar 35 hingga 90. Visualisasi ini memberikan gambaran yang lebih representatif tentang distribusi harga "normal" dengan konsentrasi data yang lebih jelas di rentang 35-90.
# Visualisasi outlier fitur price dengan returnShipment setelah penghapusan outlier
fig = px.box(df2, x="returnShipment", y="price")
fig.show()
Terlihat untuk kedua returnShipment sudah tidak ada lagi outlier.
Visualisasi ini menunjukkan bahwa Rentang harga untuk kedua returnShipment terlihat mirip. Ini menunjukkan bahwa setelah penanganan outlier, rentang harga keseluruhan untuk kedua returnShipment tidak jauh berbeda. Selain itu ada sedikit perbedaan pada median harga antara barang yang dikembalikan (69.9) dan yang tidak dikembalikan (59.9), dengan barang yang dikembalikan cenderung sedikit lebih mahal. Namun, variabilitas (IQR) dan rentang harga keseluruhan hampir sama.
#Cek jumlah data pada returnShipment untuk melihat apakah data target "returShipment" imbalance atau tidak
shipment = len(df2.returnShipment)
disimpan_count = len(df2[df2.returnShipment == 0])
dikembalikan_count = len(df2[df2.returnShipment == 1])
disimpan_percentage = round(disimpan_count/shipment*100, 2)
dikembalikan_percentage = round(dikembalikan_count/shipment*100, 2)
print('Total shipment {}'.format(shipment))
print('Barang tidak dikembalikan {}'.format(disimpan_count))
print('Persentase tidak dikembalikan {}%'.format(disimpan_percentage))
print('Barang dikembalikan {}'.format(dikembalikan_count))
print('Persentase dikembalikan {}%'.format(dikembalikan_percentage))
Total shipment 481092 Barang tidak dikembalikan 249001 Persentase tidak dikembalikan 51.76% Barang dikembalikan 232091 Persentase dikembalikan 48.24%
plt.figure(figsize=(6, 6))
ax = sns.countplot(x='returnShipment', data = df2)
plt.title('Return Shipment', fontsize=12)
for p in ax.patches:
ax.annotate(format(p.get_height(), 'd'), (p.get_x() + p.get_width() / 2., p.get_height()),
ha = 'center', va = 'center', xytext = (0, 4), textcoords = 'offset points', fontsize=8)
plt.figure(figsize=(6, 6))
total = float(len(df2))
ax = sns.countplot(x='returnShipment', data=df2)
plt.title('Persentase Return Shipment', fontsize=12)
for p in ax.patches:
ax.annotate(format(100 * p.get_height()/total, '.2f') + '%', (p.get_x() + p.get_width() / 2., p.get_height()),
ha = 'center', va = 'center', xytext = (0, 4), textcoords = 'offset points', fontsize=8)
Dari kedua visualisasi terhadap fitur target "returnShipment" di atas, terlihat bahwa jumlah observasi untuk kategori 0 (barang tidak dikembalikan) jauh lebih besar daripada jumlah observasi untuk kategori 1 (barang dikembalikan). Tepatnya, ada 249.001 atau 51.7% barang yang tidak dikembalikan dan 232.091 atau 48.2% barang yang dikembalikan.
Meskipun terdapat sedikit ketidakseimbangan kelas, perbandingan jumlah antara kedua kelas tidak terlalu ekstrem. Perbedaan antara 51.7% dan 48.2% tidak terlalu besar, jadi meskipun ada imbalance, dampaknya mungkin tidak terlalu signifikan dibandingkan jika perbedaannya sangat drastis.
# Distribusi data kategorikal Salutation
# Menghitung frekuensi kategori
salutation_counts = df2['salutation'].value_counts().reset_index()
salutation_counts.columns = ['Kategori', 'Jumlah']
# Membuat bar chart horizontal
fig = px.bar(salutation_counts, x='Jumlah', y='Kategori', orientation='h',
title='Frekuensi Salutation', labels={'Jumlah': 'Jumlah', 'Kategori': 'Salutation'},
color='Jumlah', color_continuous_scale='Earth')
fig.update_traces(texttemplate='%{x:d}', textposition='outside')
fig.update_layout(
title_x=0.5
)
# Menampilkan plot
fig.show()
# Distribusi data kategorikal State
# Menghitung frekuensi kategori
salutation_percentage = df2['salutation'].value_counts().reset_index()
# Menghitung persentase untuk setiap kategori
salutation_percentage.columns = ['Kategori', 'Jumlah']
salutation_percentage['Persentase'] = (salutation_percentage['Jumlah'] / salutation_percentage['Jumlah'].sum()) * 100
# Membuat bar chart horizontal
fig = px.bar(salutation_percentage, x='Persentase', y='Kategori', orientation='h',
title='Persentase Salutation', labels={'Persentase': 'Persentase', 'Kategori': 'Salutation'},
color='Persentase', color_continuous_scale='Earth')
fig.update_traces(texttemplate='%{x:.2f}%', textposition='outside')
fig.update_layout(
showlegend=False,
title_x=0.5
)
# Menampilkan plot
fig.show()
Visualisasi ini secara jelas menunjukkan dominasi salutation "Mrs" dalam data mencapai 96%. Meskipun "Mr" juga memiliki jumlah yang signifikan mencapai 3.5%, kategori lainnya jauh lebih sedikit. Data ini menunjukkan adanya preferensi target audiens atau populasi yang didata memang didominasi oleh perempuan yang sudah menikah.
# Melihat banyaknya pesanan tiap salutation yang mengembalikan pesanan dengan yang tidak mengembalikan pesanan
df2.groupby(["returnShipment","salutation"])["salutation"].count()
returnShipment salutation
0 Company 193
Family 1061
Mr 9864
Mrs 237644
not reported 239
1 Company 168
Family 830
Mr 6856
Mrs 224125
not reported 112
Name: salutation, dtype: int64
#visualisasi hubungan salutation dengan returnShipment
plt.figure(figsize=(8, 6))
total = float(len(df2['salutation']))
ax = sns.countplot(x="salutation", hue = "returnShipment", data=df2)
for p in ax.patches:
ax.annotate(format(100 * p.get_height()/total, '.2f') + '%', (p.get_x() + p.get_width() / 2., p.get_height()),
ha = 'center', va = 'center', xytext = (0, 6), textcoords = 'offset points', fontsize=8)
Dari semua data dengan sapaan "Mrs", 49.40% barang tidak dikembalikan dan 46.59% dikembalikan, perbedaan ini relatif kecil, menunjukkan bahwa tingkat pengembalian untuk "Mrs" hampir seimbang. Sapaan "Mr", 2.05% barang tidak dikembalikan dan 1.43% dikembalikan, proporsi barang yang tidak dikembalikan lebih tinggi daripada yang dikembalikan. Untuk "Company", "Family", dan "not reported", jumlah observasi sangat kecil, sehingga persentasenya juga kecil.
Terlihat bahwa dalam setiap kategori salutation, jumlah barang yang tidak dikembalikan cenderung lebih tinggi daripada jumlah barang yang dikembalikan, meskipun perbedaannya paling kecil pada "Mrs".
# Distribusi data kategorikal State
# Menghitung frekuensi kategori
state_counts = df2['state'].value_counts().reset_index()
state_counts.columns = ['Kategori', 'Jumlah']
# Membuat bar chart horizontal menggunakan Plotly Express
fig = px.bar(state_counts, x='Jumlah', y='Kategori', orientation='h',
title='Frekuensi State', labels={'Jumlah': 'Jumlah', 'Kategori': 'State'},
color='Jumlah', color_continuous_scale='Earth')
fig.update_traces(texttemplate='%{x:d}', textposition='outside')
fig.update_layout(title_x=0.5)
# Menampilkan plot
fig.show()
# Menghitung frekuensi kategori
state_percentage = df2['state'].value_counts().reset_index()
# Menghitung persentase untuk setiap kategori
state_percentage.columns = ['Kategori', 'Jumlah']
state_percentage['Persentase'] = (state_percentage['Jumlah'] / state_percentage['Jumlah'].sum()) * 100
# Membuat bar chart horizontal menggunakan Plotly Express dengan persentase
fig = px.bar(state_percentage, x='Persentase', y='Kategori', orientation='h',
title='Persentase State', labels={'Persentase': 'Persentase', 'Kategori': 'State'},
color='Persentase', color_continuous_scale='Earth')
fig.update_traces(texttemplate='%{x:.2f}%', textposition='outside')
fig.update_layout(
showlegend=False,
title_x=0.5
)
# Menampilkan plot
fig.show()
Negara bagian North Rhine-Westphalia memiliki persentase tertinggi, yaitu 23.1%, ini secara signifikan lebih tinggi dibandingkan negara bagian lainnya. Lower Saxony, Bavaria, dan Baden-Wuerttemberg, tiga negara bagian ini memiliki persentase yang cukup tinggi, berkisar antara 13% hingga 14%. Sebagian besar negara bagian lainnya memiliki persentase di bawah 7%, dan beberapa bahkan di bawah 1%.
#visualisasi hubungan salutation dengan returnShipment
plt.figure(figsize=(8, 8))
total = float(len(df2['state']))
ax = sns.countplot(y="state", hue="returnShipment", data=df2)
# Menambahkan anotasi persentase di atas setiap batang
for p in ax.patches:
# Menghitung persentase
percentage = 100 * p.get_width() / total
# Menentukan posisi anotasi (di sebelah kanan batang)
ax.annotate(
f'{percentage:.2f}%',
(p.get_x() + p.get_width(), p.get_y() + p.get_height() / 2.), # Posisi di kanan batang
ha='left', va='center',
xytext=(6, 0), textcoords='offset points', fontsize=8
)
plt.show()
Dari semua data negara bagain "North Rhine-Westphalia", 12% barang tidak dikembalikan dan 11% dikembalikan, perbedaan ini relatif kecil, menunjukkan bahwa tingkat pengembalian untuk negara bagain "North Rhine-Westphalia" hampir seimbang. Negara bagian lainnya juga terlihat untuk kategori barang tidak dikembalikan sedikit dibandingkan barang yang dikembalikan. Terlihat juga beberapa negara bagian dengan dengan persentase kecil dengan jumlah barang yang dikembalikan sedikit tinggi nol sekian persen daripada jumlah barang yang tidak dikembalikan.
# Salin data df2
df_train = df2.copy()
# Encoding atribut targert 'returnShipment' menggunakan LabelEncoder
from sklearn import preprocessing
le = preprocessing.LabelEncoder()
le.fit(df_train.returnShipment)
Y = le.transform(df_train.returnShipment)
# One hot encoding untuk data bertipe categorical
df_train = pd.get_dummies(data=df_train, columns=['size', 'color', 'salutation', 'state'])
df_train.head()
| orderItemID | orderDate | deliveryDate | itemID | manufacturerID | price | customerID | dateOfBirth | creationDate | returnShipment | ... | state_Hesse | state_Lower Saxony | state_Mecklenburg-Western Pomerania | state_North Rhine-Westphalia | state_Rhineland-Palatinate | state_Saarland | state_Saxony | state_Saxony-Anhalt | state_Schleswig-Holstein | state_Thuringia | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 1 | 2012-04-01 | 2012-04-03 | 186 | 25 | 69.90 | 794 | 1965-01-06 | 2011-04-25 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| 1 | 2 | 2012-04-01 | 2012-04-03 | 71 | 21 | 69.95 | 794 | 1965-01-06 | 2011-04-25 | 1 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| 2 | 3 | 2012-04-01 | 2012-04-03 | 71 | 21 | 69.95 | 794 | 1965-01-06 | 2011-04-25 | 1 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| 3 | 4 | 2012-04-02 | 2012-04-07 | 22 | 14 | 39.90 | 808 | 1959-11-09 | 2012-01-04 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 |
| 4 | 5 | 2012-04-02 | 2012-04-05 | 151 | 53 | 29.90 | 825 | 1964-07-11 | 2011-02-16 | 0 | ... | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 |
5 rows × 63 columns
# Siapkan fitur untuk training dengan membuang kelas label (returnShipment)
X = df_train.drop("returnShipment", axis=1)
# Mengubah data bertipe datetime untuk keperluan klasifikasi
import datetime as dt
X['orderDate'] = X['orderDate'].map(dt.datetime.toordinal)
X['deliveryDate'] = X['deliveryDate'].map(dt.datetime.toordinal)
X['dateOfBirth'] = X['dateOfBirth'].map(dt.datetime.toordinal)
X['creationDate'] = X['creationDate'].map(dt.datetime.toordinal)
# Split Dataset, 80% sebagai data train dan 20% sisanya sebagai data test
from sklearn.model_selection import train_test_split
X_train, X_test, Y_train, Y_test = train_test_split(X, Y, test_size=0.2, random_state=42)
# Simpan nama kolom untuk keperluan prediksi nanti
import pickle
with open('train_v1.pickle', 'wb') as fp:
pickle.dump(X_train.columns, fp)
from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score
from sklearn.metrics import classification_report, make_scorer
from sklearn.model_selection import cross_val_score
from sklearn.metrics import confusion_matrix
# Klasifikasi dengan Naive Bayes
from sklearn.naive_bayes import GaussianNB
# Inisialisasi Naive Bayes
nb_clf = GaussianNB()
# Melatih model Naive Bayes dengan data latih
nb_clf.fit(X_train, Y_train)
# Menggunakan model Naive Bayes untuk melakukan prediksi pada data uji
nb_pred = nb_clf.predict(X_test)
# Menghitung masing-masing metrik evaluasi
nb_accuracy = accuracy_score(Y_test, nb_pred)
nb_precision = precision_score(Y_test, nb_pred)
nb_recall = recall_score(Y_test, nb_pred)
nb_f1 = f1_score(Y_test, nb_pred)
# Menampilkan masing-masing metrik evaluasi
print(f"Accuracy: {nb_accuracy:.3f}")
print(f"Precision: {nb_precision:.3f}")
print(f"Recall: {nb_recall:.3f}")
print(f"F1 Score: {nb_f1:.3f}")
print("\n")
print(classification_report(Y_test, nb_pred))
Accuracy: 0.556
Precision: 0.545
Recall: 0.462
F1 Score: 0.500
precision recall f1-score support
0 0.56 0.64 0.60 49937
1 0.55 0.46 0.50 46282
accuracy 0.56 96219
macro avg 0.55 0.55 0.55 96219
weighted avg 0.55 0.56 0.55 96219
# Klasifikasi dengan Decision Tree
from sklearn import tree
# Inisialisasi Decision Tree
dt_clf = tree.DecisionTreeClassifier()
# Melatih model Decsion Tree dengan data latih
dt_clf.fit(X_train, Y_train)
# Menggunakan model Decision Tree untuk melakukan prediksi pada data uji
dt_pred = dt_clf.predict(X_test)
# Menghitung masing-masing metrik evaluasi
dt_accuracy = accuracy_score(Y_test, dt_pred)
dt_precision = precision_score(Y_test, dt_pred)
dt_recall = recall_score(Y_test, dt_pred)
dt_f1 = f1_score(Y_test, dt_pred)
# Menampilkan masing-masing metrik evaluasi
print(f"Accuracy: {dt_accuracy:.3f}")
print(f"Precision: {dt_precision:.3f}")
print(f"Recall: {dt_recall:.3f}")
print(f"F1 Score: {dt_f1:.3f}")
print("\n")
print(classification_report(Y_test, dt_pred))
Accuracy: 0.579
Precision: 0.560
Recall: 0.573
F1 Score: 0.567
precision recall f1-score support
0 0.60 0.58 0.59 49937
1 0.56 0.57 0.57 46282
accuracy 0.58 96219
macro avg 0.58 0.58 0.58 96219
weighted avg 0.58 0.58 0.58 96219
# Klasifikasi dengan Random Forest
from sklearn.ensemble import RandomForestClassifier
# Inisialisasi RandomForestClassifier
rf_clf = RandomForestClassifier(max_depth = 10,
n_estimators=100,
random_state=42)
# Melatih model Random Forest dengan data latih
rf_clf.fit(X_train, Y_train)
# Menggunakan model Random Forest untuk melakukan prediksi pada data uji
rf_pred = rf_clf.predict(X_test)
# Menghitung masing-masing metrik evaluasi
rf_accuracy = accuracy_score(Y_test, rf_pred)
rf_precision = precision_score(Y_test, rf_pred)
rf_recall = recall_score(Y_test, rf_pred)
rf_f1 = f1_score(Y_test, rf_pred)
# Menampilkan masing-masing metrik evaluasi
print(f"Accuracy: {rf_accuracy:.3f}")
print(f"Precision: {rf_precision:.3f}")
print(f"Recall: {rf_recall:.3f}")
print(f"F1 Score: {rf_f1:.3f}")
print("\n")
print(classification_report(Y_test, rf_pred))
Accuracy: 0.591
Precision: 0.569
Recall: 0.619
F1 Score: 0.593
precision recall f1-score support
0 0.62 0.57 0.59 49937
1 0.57 0.62 0.59 46282
accuracy 0.59 96219
macro avg 0.59 0.59 0.59 96219
weighted avg 0.59 0.59 0.59 96219
# Klasifikasi dengan XGBoost
import xgboost as xgb
from xgboost import XGBClassifier
# Inisialisasi XGBClassifier
xgb_clf = XGBClassifier(objective = 'binary:logistic', booster = 'gbtree',
learning_rate = 0.05, max_depth = 8,
subsample = 0.7, min_child_weight = 5,
gamma = 0.1, colsample_bytree = 0.7,
n_estimators=200, random_state=42)
# Melatih model XGBoost dengan data latih
xgb_clf.fit(X_train, Y_train)
# Menggunakan model XGBoost untuk melakukan prediksi pada data uji
xgb_pred = xgb_clf.predict(X_test)
# Menghitung masing-masing metrik evaluasi
xgb_accuracy = accuracy_score(Y_test, xgb_pred)
xgb_recall = recall_score(Y_test, xgb_pred)
xgb_precision = precision_score(Y_test, xgb_pred)
xgb_f1 = f1_score(Y_test, xgb_pred)
# Menampilkan masing-masing metrik evaluasi
print(f"Accuracy: {xgb_accuracy:.3f}")
print(f"Precision: {xgb_precision:.3f}")
print(f"Recall: {xgb_recall:.3f}")
print(f"F1 Score: {xgb_f1:.3f}")
print("\n")
print(classification_report(Y_test, xgb_pred))
Accuracy: 0.621
Precision: 0.600
Recall: 0.638
F1 Score: 0.618
precision recall f1-score support
0 0.64 0.61 0.62 49937
1 0.60 0.64 0.62 46282
accuracy 0.62 96219
macro avg 0.62 0.62 0.62 96219
weighted avg 0.62 0.62 0.62 96219
Dalam kasus prediksi pengembalian barang atau tidak, Recall untuk memastikan semua pesanan yang mungkin akan dikembalikan bisa dideteksi dengan baik. Sehingga fokus pada minimisasi False Negatives untuk memastikan bahwa barang yang berpotensi dikembalikan teridentifikasi dengan baik. False Negatives (pesanan yang akan dikembalikan tetapi diprediksi tidak akan dikembalikan) berisiko tinggi karena akan menyebabkan biaya tambahan seperti stok tidak siap untuk dijual ulang, pelanggan kecewa karena pengembalian tidak diantisipasi.
Presisi untuk meminimalkan prediksi pengembalian yang salah atau untuk memastikan bahwa prediksi pengembalian barang benar-benar akurat. Dalam kasus pengembalian barang, baik precision dan recall penting, karena Recall diperlukan untuk menangkap semua pengembalian barang sedangkan Precision diperlukan untuk mengurangi kesalahan prediksi "barang akan dikembalikan" yang sebenarnya tidak akan dikembalikan. Untuk mencari keseimbangan antara recall dan presisi dapat menggunakan F1-score. F1-score memberikan keseimbangan antara precision dan recall.
Sehingga untuk model yang akan digunakan untuk prediksi dilihat dari hasil Recall dan F1-Score.
# Hasil evaluasi untuk berbagai model
model_evaluations = {
"Naive Bayes": {"Accuracy": nb_accuracy, "Precision": nb_precision, "Recall": nb_recall, "F1 Score": nb_f1},
"Decision Tree": {"Accuracy": dt_accuracy, "Precision": dt_precision, "Recall": dt_recall, "F1 Score": dt_f1},
"Random Forest": {"Accuracy": rf_accuracy, "Precision": rf_precision, "Recall": rf_recall, "F1 Score": rf_f1},
"XGBoost": {"Accuracy": xgb_accuracy, "Precision": xgb_precision, "Recall": xgb_recall, "F1 Score": xgb_f1}
}
# Menentukan model terbaik berdasarkan F1-score
best_model = max(model_evaluations, key=lambda x: model_evaluations[x]["Recall"])
print(f"Model terbaik adalah {best_model} dengan Recall {model_evaluations[best_model]['Recall']:.3f}.")
Model terbaik adalah XGBoost dengan Recall 0.638.
# Menentukan model terbaik berdasarkan F1-score
best_model = max(model_evaluations, key=lambda x: model_evaluations[x]["Recall"])
print(f"Model terbaik adalah {best_model} dengan F1-Score {model_evaluations[best_model]['F1 Score']:.3f}.")
Model terbaik adalah XGBoost dengan F1-Score 0.618.
Recall yang lebih tinggi menunjukkan bahwa model lebih baik dalam mengidentifikasi pengembalian barang. Dari hasil klasifikasi beberapa model di atas, model dengan nilai Recall dan F1-Score tertinggi adalah model XGBoost. Ini berarti bahwa XGBoost lebih baik dalam mendeteksi pengembalian barang dibandingkan model lainnya.
# Cross Validation dengan Random Forest
def classification_report_with_score(y_true, y_pred):
# Menampilkan classification report dan confusion matrix
print(classification_report(y_true, y_pred, zero_division=0))
print(confusion_matrix(y_true, y_pred))
return accuracy_score(y_true, y_pred)
# Inisialisasi RandomForestClassifier
rf_clf = RandomForestClassifier(max_depth = 10,
n_estimators=100,
random_state=42)
# Melatih model Random Forest dengan data latih
rf_clf.fit(X_train, Y_train)
# Evaluasi dengan cross-validation
scores = cross_val_score(rf_clf, X=X, y=Y, cv=5,
scoring=make_scorer(classification_report_with_score))
# Menampilkan hasil cross-validation
print("\nSummary Cross Validatoin: ")
print(f"Cross-validation scores: {scores}")
print(f"Mean accuracy: {scores.mean():.3f}")
print(f"Standard deviation: {scores.std():.3f}")
precision recall f1-score support
0 0.86 0.00 0.01 49801
1 0.48 1.00 0.65 46418
accuracy 0.48 96219
macro avg 0.67 0.50 0.33 96219
weighted avg 0.68 0.48 0.32 96219
[[ 137 49664]
[ 22 46396]]
precision recall f1-score support
0 0.52 1.00 0.68 49800
1 0.00 0.00 0.00 46419
accuracy 0.52 96219
macro avg 0.26 0.50 0.34 96219
weighted avg 0.27 0.52 0.35 96219
[[49800 0]
[46419 0]]
precision recall f1-score support
0 0.48 0.45 0.46 49800
1 0.44 0.47 0.45 46418
accuracy 0.46 96218
macro avg 0.46 0.46 0.46 96218
weighted avg 0.46 0.46 0.46 96218
[[22538 27262]
[24791 21627]]
precision recall f1-score support
0 0.44 0.48 0.46 49800
1 0.39 0.35 0.37 46418
accuracy 0.42 96218
macro avg 0.42 0.42 0.42 96218
weighted avg 0.42 0.42 0.42 96218
[[24096 25704]
[30133 16285]]
precision recall f1-score support
0 0.00 0.00 0.00 49800
1 0.48 1.00 0.65 46418
accuracy 0.48 96218
macro avg 0.24 0.50 0.33 96218
weighted avg 0.23 0.48 0.31 96218
[[ 0 49800]
[ 0 46418]]
Summary Cross Validatoin:
Cross-validation scores: [0.4836155 0.5175693 0.45900975 0.41968239 0.48242533]
Mean accuracy: 0.472
Standard deviation: 0.032
# Cross Validation model XGBoost
def classification_report_with_score(y_true, y_pred):
# Menampilkan classification report dan confusion matrix
print(classification_report(y_true, y_pred, zero_division=0))
print(confusion_matrix(y_true, y_pred))
return accuracy_score(y_true, y_pred)
# Inisialisasi XGBoost
xgb_clf = XGBClassifier(objective = 'binary:logistic', booster = 'gbtree',
learning_rate = 0.05, max_depth = 8,
subsample = 0.7, min_child_weight = 5,
gamma = 0.1, colsample_bytree = 0.7,
n_estimators=200, random_state=42)
# Melatih model XGBoost dengan data latih
xgb_clf.fit(X_train, Y_train)
# Evaluasi dengan cross-validation
scores = cross_val_score(xgb_clf, X=X, y=Y, cv=5,
scoring=make_scorer(classification_report_with_score))
# Menampilkan hasil cross-validation
print("\nSummary Cross Validation: ")
print(f"Cross-validation scores: {scores}")
print(f"Mean accuracy: {scores.mean():.3f}")
print(f"Standard deviation: {scores.std():.3f}")
precision recall f1-score support
0 0.68 0.22 0.33 49801
1 0.51 0.89 0.65 46418
accuracy 0.54 96219
macro avg 0.60 0.55 0.49 96219
weighted avg 0.60 0.54 0.49 96219
[[10842 38959]
[ 5067 41351]]
precision recall f1-score support
0 0.52 1.00 0.68 49800
1 0.64 0.01 0.01 46419
accuracy 0.52 96219
macro avg 0.58 0.50 0.35 96219
weighted avg 0.58 0.52 0.36 96219
[[49652 148]
[46158 261]]
precision recall f1-score support
0 0.39 0.16 0.23 49800
1 0.45 0.72 0.55 46418
accuracy 0.43 96218
macro avg 0.42 0.44 0.39 96218
weighted avg 0.42 0.43 0.39 96218
[[ 8166 41634]
[12812 33606]]
precision recall f1-score support
0 0.46 0.51 0.49 49800
1 0.41 0.36 0.38 46418
accuracy 0.44 96218
macro avg 0.44 0.44 0.44 96218
weighted avg 0.44 0.44 0.44 96218
[[25573 24227]
[29585 16833]]
precision recall f1-score support
0 0.00 0.00 0.00 49800
1 0.48 1.00 0.65 46418
accuracy 0.48 96218
macro avg 0.24 0.50 0.33 96218
weighted avg 0.23 0.48 0.31 96218
[[ 0 49800]
[ 0 46418]]
Summary Cross Validation:
Cross-validation scores: [0.54243964 0.5187437 0.43413914 0.44072835 0.48242533]
Mean accuracy: 0.484
Standard deviation: 0.042
Random Forest memiliki performa moderat dengan tingkat akurasi yang tidak terlalu tinggi. XGBoost memberikan performa yang lebih baik dibandingkan Random Forest. XGBoost memiliki akurasi rata-rata yang lebih tinggi (48.4%) dibandingkan Random Forest (47.2%). Ini menunjukkan bahwa XGBoost lebih efektif dalam menangkap pola dalam data.
# Confusion Matrix dengan XGBoost
plt.figure(figsize = (6, 4))
sns.heatmap(confusion_matrix(Y_test, xgb_pred), annot = True, fmt="d", cmap="Blues")
plt.xlabel("Predicted Label")
plt.ylabel("True Label")
plt.title("Confusion Matrix")
plt.show()
Model memiliki performa yang lumayan untuk mendeteksi barang yang dikembalikan (recall 63.8%), yang penting untuk mengidentifikasi semua potensi pengembalian.
# Simpan model dengan joblib
import joblib as jb
jb.dump(xgb_clf, "model_xgb.joblib")
['model_xgb.joblib']
df3 = pd.read_csv("E:\\data\\orders_class.txt", sep=';', na_values=["?"])
df3.head()
| orderItemID | orderDate | deliveryDate | itemID | size | color | manufacturerID | price | customerID | salutation | dateOfBirth | state | creationDate | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 1 | 2013-04-01 | 2013-04-03 | 2347 | 43 | magenta | 1 | 89.9 | 12489 | Mrs | 1963-04-26 | Hesse | 2012-04-23 |
| 1 | 2 | 2013-04-01 | 2013-04-03 | 2741 | 43 | grey | 1 | 99.9 | 12489 | Mrs | 1963-04-26 | Hesse | 2012-04-23 |
| 2 | 3 | 2013-04-01 | 2013-04-03 | 2514 | 9 | ecru | 19 | 79.9 | 12489 | Mrs | 1963-04-26 | Hesse | 2012-04-23 |
| 3 | 4 | 2013-04-01 | 2013-05-06 | 2347 | 42 | brown | 1 | 89.9 | 12489 | Mrs | 1963-04-26 | Hesse | 2012-04-23 |
| 4 | 5 | 2013-04-01 | NaN | 2690 | 43 | grey | 1 | 119.9 | 12489 | Mrs | 1963-04-26 | Hesse | 2012-04-23 |
df3.info()
<class 'pandas.core.frame.DataFrame'> RangeIndex: 50078 entries, 0 to 50077 Data columns (total 13 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 orderItemID 50078 non-null int64 1 orderDate 50078 non-null object 2 deliveryDate 45810 non-null object 3 itemID 50078 non-null int64 4 size 50078 non-null object 5 color 50078 non-null object 6 manufacturerID 50078 non-null int64 7 price 50078 non-null float64 8 customerID 50078 non-null int64 9 salutation 50078 non-null object 10 dateOfBirth 44909 non-null object 11 state 50078 non-null object 12 creationDate 50078 non-null object dtypes: float64(1), int64(4), object(8) memory usage: 5.0+ MB
# Mengubah semua tipe data fitur dengan format tanggal menjadi datetime
kategori_waktu = ['orderDate', 'deliveryDate', 'dateOfBirth', 'creationDate']
#Looping untuk merubah type data'
for column in kategori_waktu:
df3[column] = df3[column].astype('datetime64[ns]')
df3.dtypes
orderItemID int64 orderDate datetime64[ns] deliveryDate datetime64[ns] itemID int64 size object color object manufacturerID int64 price float64 customerID int64 salutation object dateOfBirth datetime64[ns] state object creationDate datetime64[ns] dtype: object
# Mengubah semua tipe data fitur objek menjadi kategori
kategori_objek = ['size', 'color', 'salutation', 'state']
#Looping untuk merubah type data'
for column in kategori_objek:
df3[column] = df3[column].astype('category')
df3.dtypes
orderItemID int64 orderDate datetime64[ns] deliveryDate datetime64[ns] itemID int64 size category color category manufacturerID int64 price float64 customerID int64 salutation category dateOfBirth datetime64[ns] state category creationDate datetime64[ns] dtype: object
df3.isnull().sum()
orderItemID 0 orderDate 0 deliveryDate 4268 itemID 0 size 0 color 0 manufacturerID 0 price 0 customerID 0 salutation 0 dateOfBirth 5169 state 0 creationDate 0 dtype: int64
Data kosong pada data yang akan diprediksi yaitu pada fitur deliveryDate dan dateOfBirth. Proses menangani data kosong ini sama seperti pada proses untuk data training
# Melihat nilai unik dari DeliveryDate
df3['deliveryDate'].dt.year.unique()
array([2013., nan, 1990.])
df3[df3['deliveryDate'].dt.year == 1990][['orderDate', 'deliveryDate']].head(10)
| orderDate | deliveryDate | |
|---|---|---|
| 562 | 2013-04-01 | 1990-12-31 |
| 973 | 2013-04-01 | 1990-12-31 |
| 1278 | 2013-04-01 | 1990-12-31 |
| 1279 | 2013-04-01 | 1990-12-31 |
| 1512 | 2013-04-01 | 1990-12-31 |
| 1607 | 2013-04-01 | 1990-12-31 |
| 1643 | 2013-04-02 | 1990-12-31 |
| 1828 | 2013-04-02 | 1990-12-31 |
| 1829 | 2013-04-02 | 1990-12-31 |
| 1830 | 2013-04-02 | 1990-12-31 |
import random
# Fungsi untuk mengganti tahun, bulan, dan hari deliveryDate mengacu pada orderDate
def adjust_delivery_date(row):
# Memeriksa apakah tahun deliveryDate adalah 1990
if row['deliveryDate'].year == 1990:
# Ambil bulan dan tahun dari orderDate
year = row['orderDate'].year
month = row['orderDate'].month
# Ambil + 3-5 hari dari orderDate
day = row['orderDate'].day + random.choice([3, 5])
# Menghindari kesalahan jika hari yang ditambahkan melebihi batas bulan
try:
adjusted_date = row['deliveryDate'].replace(year=year, month=month, day=day)
except ValueError:
# Jika hari yang ditambahkan melebihi batas, set ke hari terakhir bulan tersebut
adjusted_date = row['deliveryDate'].replace(year=year, month=month, day=1) + pd.Timedelta(days=-1)
return adjusted_date
return row['deliveryDate']
# Terapkan fungsi dengan modus
df3['deliveryDate'] = df3.apply(adjust_delivery_date, axis=1)
# Melihat nilai unik dari DeliveryDate
df3['deliveryDate'].dt.year.unique()
array([2013., nan])
# Fungsi untuk mengisi deliveryDate berdasarkan orderDate
def fill_delivery_date(row):
if pd.isna(row['deliveryDate']): # Jika deliveryDate kosong
# Tambahkan antara 2 hingga 5 hari ke orderDate
penambahan_hari = random.randint(2, 5)
return row['orderDate'] + pd.Timedelta(days=penambahan_hari)
return row['deliveryDate'] # Jika tidak kosong, biarkan deliveryDate tetap
# Terapkan fungsi ke dataframe
df3['deliveryDate'] = df3.apply(fill_delivery_date, axis=1)
# Melihat nilai unik dari DeliveryDate
df3['deliveryDate'].dt.year.unique()
array([2013], dtype=int64)
df3.isnull().sum()
orderItemID 0 orderDate 0 deliveryDate 0 itemID 0 size 0 color 0 manufacturerID 0 price 0 customerID 0 salutation 0 dateOfBirth 5169 state 0 creationDate 0 dtype: int64
# Melihat nilai maksimal dan minimal dateOfBirth
print("Tanggal Max dateOfBirth :", df3.dateOfBirth.max())
print("Tanggal Min dateOfBirth :", df3.dateOfBirth.min())
Tanggal Max dateOfBirth : 2012-11-18 00:00:00 Tanggal Min dateOfBirth : 1900-11-19 00:00:00
# Melihat nilai unik dari dateOfBirth
df3['dateOfBirth'].dt.year.unique()
array([1963., 1956., 1967., 1958., nan, 1986., 1957., 1953., 1952.,
1962., 1971., 1976., 1975., 1950., 1973., 1960., 1968., 1979.,
1951., 1955., 1970., 1959., 1946., 1972., 1949., 1969., 1974.,
1965., 1961., 1966., 1944., 1981., 1964., 1983., 1978., 1954.,
1977., 1945., 1985., 1900., 1947., 1982., 1987., 1948., 1993.,
1984., 1990., 1992., 1936., 1980., 1938., 1988., 1901., 1937.,
1942., 2011., 1994., 1943., 1999., 1941., 1940., 1991., 1939.,
1931., 1935., 1933., 2010., 1989., 1925., 1934., 2012., 1929.,
1928., 2009., 1998., 1930., 1919.])
# Hitung median tahun yang valid (1935-1997)
tahun_valid = df3[df3['dateOfBirth'].dt.year.between(1935, 1997)]['dateOfBirth'].dt.year
median_tahun = tahun_valid.median()
# Ganti tahun yang tidak valid (di bawah 1935 atau di atas 1997) dengan median
df3['dateOfBirth'] = df3['dateOfBirth'].apply(
lambda x: pd.Timestamp(f"{int(median_tahun)}-{x.month:02d}-{x.day:02d}")
if x.year < 1935 or x.year > 1997 else x
)
# Melihat nilai maksimal dan minimal dateOfBirth setelah mengubah dengan median
print("Tanggal Max dateOfBirth :", df3.dateOfBirth.max())
print("Tanggal Min dateOfBirth :", df3.dateOfBirth.min())
Tanggal Max dateOfBirth : 1994-06-20 00:00:00 Tanggal Min dateOfBirth : 1935-03-09 00:00:00
# Melihat nilai unik dari dateOfBirth
df3['dateOfBirth'].dt.year.unique()
array([1963., 1956., 1967., 1958., nan, 1986., 1957., 1953., 1952.,
1962., 1971., 1976., 1975., 1950., 1973., 1960., 1968., 1979.,
1951., 1955., 1970., 1959., 1946., 1972., 1949., 1969., 1974.,
1965., 1961., 1966., 1944., 1981., 1964., 1983., 1978., 1954.,
1977., 1945., 1985., 1947., 1982., 1987., 1948., 1993., 1984.,
1990., 1992., 1936., 1980., 1938., 1988., 1937., 1942., 1994.,
1943., 1941., 1940., 1991., 1939., 1935., 1989.])
# Fungsi untuk mengganti tahun yang hilang dengan median
def replace_with_median(row):
if pd.isna(row): # Jika data kosong (NaN) atau NaT
# Jika data kosong, ganti tahun dengan median
return pd.Timestamp(f"{int(median_tahun)}-01-01")
# Jika data tidak kosong, kembalikan baris tanpa perubahan
return row
# Terapkan fungsi ke dataframe
df3['dateOfBirth'] = df3['dateOfBirth'].apply(replace_with_median)
df3.isnull().sum()
orderItemID 0 orderDate 0 deliveryDate 0 itemID 0 size 0 color 0 manufacturerID 0 price 0 customerID 0 salutation 0 dateOfBirth 0 state 0 creationDate 0 dtype: int64
# Cek hasil fitur size
df3['size'].unique()
['43', '9', '42', '41', '7+', ..., '3634', '14', '3834', '56', '46+'] Length: 100 Categories (100, object): ['1', '10', '10+', '104', ..., 'unsized', 'xl', 'xxl', 'xxxl']
# Normalisasi dan buang tanda '+'
df3['size'] = df3['size'].str.upper() # Ubah ke kapital semua
df3['size'] = df3['size'].str.replace(r'\+', '', regex=True) # Hapus tanda '+'
# Mengelompokkan ukuran berdasarkan angka
def categorize_size(size):
# Kategori ukuran standar
size_categories = ['XS', 'S', 'M', 'L', 'XL', 'XXL', 'XXXL']
if size in size_categories: # Jika sudah dalam kategori standar
return size
try:
size_num = int(size)
# Kategorisasi berdasarkan ukuran angka
if size_num <= 10:
return 'XS'
elif 11 <= size_num <= 35:
return 'S'
elif 36 <= size_num <= 42:
return 'M'
elif 43 <= size_num <= 50:
return 'L'
elif 51 <= size_num <= 200:
return 'XL'
elif 201 <= size_num <= 1000:
return 'XXL'
elif size_num > 1000:
return 'XXXL'
except ValueError:
return 'UNSIZED'
# Terapkan fungsi ke dataframe untuk mengkategorikan ukuran
df3['size'] = df3['size'].apply(categorize_size)
# Cek hasil fitur size
df3['size'].unique()
array(['L', 'XS', 'M', 'S', 'UNSIZED', 'XL', 'XXL', 'XXXL'], dtype=object)
# Distribusi data kategorikal Size
# Menghitung frekuensi kategori
size_counts = df3['size'].value_counts().reset_index()
size_counts.columns = ['Kategori', 'Jumlah']
# Membuat bar chart horizontal menggunakan Plotly Express
fig = px.bar(size_counts, x='Jumlah', y='Kategori', orientation='h',
title='Frekuensi Fitur Size', labels={'Jumlah': 'Jumlah', 'Kategori': 'Size'},
color='Jumlah', color_continuous_scale='Earth')
fig.update_layout(
title_x=0.5,
)
# Menampilkan plot
fig.show()
# Cek nilai unik color
df3.color.unique()
['magenta', 'grey', 'ecru', 'brown', 'blue', ..., 'leopard', 'almond', 'gold', 'lemon', 'antique pink'] Length: 66 Categories (66, object): ['almond', 'ancient', 'anthracite', 'antique pink', ..., 'terracotta', 'turquoise', 'white', 'yellow']
# Hitung frekuensi masing-masing kategori
color_counts = df3['color'].value_counts()
# Tentukan threshold untuk kategori yang jarang muncul
threshold = 1000 # Kemunculan kurang dari 1000 kali
# Ganti color yang jarang muncul dengan 'other'
df3['color'] = df3['color'].apply(lambda x: x if color_counts[x] >= threshold else 'other')
# Cek nilai unik color
df3.color.unique()
array(['other', 'grey', 'ecru', 'brown', 'blue', 'white', 'purple',
'green', 'black', 'stained', 'red', 'ocher', 'pink', 'olive',
'denim', 'aquamarine', 'petrol'], dtype=object)
df3['color'].value_counts().head()
black 8346 other 6853 green 5226 blue 4794 grey 4443 Name: color, dtype: int64
df_pred = df3.copy()
df_pred.dtypes
orderItemID int64 orderDate datetime64[ns] deliveryDate datetime64[ns] itemID int64 size object color object manufacturerID int64 price float64 customerID int64 salutation category dateOfBirth datetime64[ns] state category creationDate datetime64[ns] dtype: object
df_pred["size"] = df_pred["size"].astype("category")
df_pred["color"] = df_pred["color"].astype("category")
df_pred.dtypes
orderItemID int64 orderDate datetime64[ns] deliveryDate datetime64[ns] itemID int64 size category color category manufacturerID int64 price float64 customerID int64 salutation category dateOfBirth datetime64[ns] state category creationDate datetime64[ns] dtype: object
# Menyesuaikan tipe data kategori
from pandas.api.types import CategoricalDtype
kolom_kategori = ['size', 'color', 'salutation', 'state']
for kolom in kolom_kategori:
data_kategori = CategoricalDtype(categories = df2[kolom].cat.categories, ordered=True)
df_pred[kolom] = df_pred[kolom].astype(data_kategori)
# Mengaplikasikan one hot encoding untuk data bertipe categorical pada data prediksi
df_pred = pd.get_dummies(data=df_pred, columns=['size', 'color', 'salutation', 'state'])
# Mengubah fitur tipe data datetime menjadi ordinal untuk memudahkan model machine learning memproses
import datetime as dt
df_pred['orderDate'] = df_pred['orderDate'].map(dt.datetime.toordinal)
df_pred['deliveryDate'] = df_pred['deliveryDate'].map(dt.datetime.toordinal)
df_pred['dateOfBirth'] = df_pred['dateOfBirth'].map(dt.datetime.toordinal)
df_pred['creationDate'] = df_pred['creationDate'].map(dt.datetime.toordinal)
with open ('train_v1.pickle', 'rb') as fp:
X_train_column = list(pickle.load(fp))
df_pred = df_pred[X_train_column]
#Prediksi dengan model yang sudah disimpan
clf = jb.load("model_xgb.joblib")
result = clf.predict(df_pred)
print(result[:100])
[1 1 1 1 1 0 1 1 1 1 0 1 0 0 1 0 0 0 1 0 1 1 1 1 0 1 1 1 1 0 0 1 0 0 0 0 0 0 1 0 1 1 1 1 0 1 0 1 0 0 0 1 1 1 0 1 1 0 1 1 0 1 0 1 1 1 1 1 1 0 1 1 0 0 0 0 0 1 1 1 1 1 0 0 1 0 1 0 0 0 1 1 0 1 1 1 1 1 1 0]
#Hasil prediksi
shipment_predict = len(result)
save_count = len(result[result == 0]) #barang disimpan
return_count = len(result[result == 1]) #barang dikembalikan
save_percentage = round(save_count/shipment_predict*100, 2)
return_percentage = round(return_count/shipment_predict*100, 2)
print('Hasil Prediksi:')
print('Total shipment {}'.format(shipment_predict))
print('Barang tidak dikembalikan {}'.format(save_count))
print('Persentase tidak dikembalikan {}%'.format(save_percentage))
print('Barang dikembalikan {}'.format(return_count))
print('Persentase dikembalikan {}%'.format(return_percentage))
Hasil Prediksi: Total shipment 50078 Barang tidak dikembalikan 16711 Persentase tidak dikembalikan 33.37% Barang dikembalikan 33367 Persentase dikembalikan 66.63%
#Visualisasi jumlah data prediksi yang tidak mengembalikan dan mengembalikan
plt.figure(figsize=(6, 6))
ax = sns.countplot(x=result, data = df_pred)
plt.title('Pediksi Return Shipment')
plt.xlabel('Return Shipment')
plt.ylabel('Count Return Shipment')
for p in ax.patches:
ax.annotate(format(p.get_height(), 'd'), (p.get_x() + p.get_width() / 2., p.get_height()),
ha = 'center', va = 'center', xytext = (0, 6), textcoords = 'offset points', fontsize=8)
#Visualisasi persentase data prediksi yang tidak mengembalikan dan mengembalikan
plt.figure(figsize=(6, 6))
total = float(len(df_pred))
ax = sns.countplot(x=result, data=df_pred)
plt.title('Persentase Prediksi Return Shipment', fontsize=12)
for p in ax.patches:
ax.annotate(format(100 * p.get_height()/total, '.2f') + '%', (p.get_x() + p.get_width() / 2., p.get_height()),
ha = 'center', va = 'center', xytext = (0, 6), textcoords = 'offset points', fontsize=8)
# Jika panjang berbeda
if len(Y_test) != len(result):
min_length = min(len(Y_test), len(result))
Y_test = Y_test[:min_length]
result = result[:min_length]
accuracy = accuracy_score(Y_test, result)
precision = precision_score(Y_test, result)
recall = recall_score(Y_test, result)
f1 = f1_score(Y_test, result)
print("Accuracy:", accuracy)
print("Precision:", precision)
print("Recall:", recall)
print("F1-score:", f1)
# Menampilkan confusion matrix
conf_matrix = confusion_matrix(Y_test, result)
print("\nConfusion Matrix:\n", conf_matrix)
# Menampilkan laporan klasifikasi
print("\nClassification Report:\n", classification_report(Y_test, result))
Accuracy: 0.4921921801988897
Precision: 0.48179338867743576
Recall: 0.6638860210613257
F1-score: 0.5583689347365496
Confusion Matrix:
[[ 8572 17291]
[ 8139 16076]]
Classification Report:
precision recall f1-score support
0 0.51 0.33 0.40 25863
1 0.48 0.66 0.56 24215
accuracy 0.49 50078
macro avg 0.50 0.50 0.48 50078
weighted avg 0.50 0.49 0.48 50078
overall_recall = recall_score(Y_test, result, average=None)
print(f"Recall untuk masing-masing label: {overall_recall}")
# Recall untuk label 1
print(f"Recall untuk Label 1: {overall_recall[1]:.3f}")
Recall untuk masing-masing label: [0.33143873 0.66388602] Recall untuk Label 1: 0.664
Hasil Prediksi:
Hasil Recall:
Recall untuk label 1 cukup tinggi, karena fokus utama mengidentifikasi barang yang akan dikembalikan maka model ini cukup baik.